#include "Fifo.h"
#include <errno.h>
#include <sstream>

#include <BaseException.h>
#include <LockFile.h>

Fifo::Fifo( const char projectId )
: m_Nombre(""), m_ProjectId(projectId), fd(-1), m_ExitHandler(NULL), 
m_CreatorPid(0), m_ProtectAccess(false) 
{

}

Fifo::Fifo(const std::string& nombre, const int modoCreacion, 
		   const char projectId, const bool protectAccess)
: m_Nombre(""), m_ProjectId(projectId), fd(-1), m_ExitHandler(NULL), 
m_CreatorPid(0), m_ProtectAccess(protectAccess)
{
	this->crear(nombre, modoCreacion, protectAccess);
}

Fifo::~Fifo() {
	this->destruir();
}

void Fifo::crear(const std::string& nombre, const int modoCreacion, const bool protectAccess)
{
	if(!m_NombreLock.empty())
		throw BaseException("El fifo "+nombre+" ya se encuentra creado");
	
	bool crearSemaforo = true;
	bool fifoFileCreated = true;
	
	// Me aseguro que la creacion del fifo sea sincronizada
	std::string nombreLock = nombre + ".lockfifo";
	LockFile lockFile(nombreLock);
	lockFile.tomarLock();
	
	int result = mknod ( static_cast<const char*>(nombre.c_str()),S_IFIFO|0666,0 );
	if(result < 0)
	{
		if(modoCreacion == CreationMode::DEBE_EXISTIR && errno != EEXIST)
		{
			lockFile.liberarLock();
			lockFile.cerrar();
			lockFile.eliminar();
			std::stringstream eMsg;
			eMsg << "El fifo "+ nombre +" no existe.";
			eMsg << " Error " << errno << ":" << strerror(errno);
			throw BaseException(eMsg.str(), errno);
		}
		else if(modoCreacion == CreationMode::NO_DEBE_EXISTIR && errno == EEXIST)
		{
			lockFile.liberarLock();
			lockFile.cerrar();
			lockFile.eliminar();
			std::stringstream eMsg;
			eMsg << "El fifo "+ nombre +" ya existe.";
			eMsg << " Error " << errno << ":" << strerror(errno);
			throw BaseException(eMsg.str(), errno);
		}
		else if(errno == EEXIST)
		{
			crearSemaforo = false;
			fifoFileCreated = false;
		}
	}
	else if(modoCreacion == CreationMode::DEBE_EXISTIR)
	{
		m_Nombre = nombre;
		this->eliminar();
		lockFile.liberarLock();
		lockFile.cerrar();
		lockFile.eliminar();
		throw BaseException("El archivo de fifo "+nombre+" no existe");
	}
	
	// Me guardo los nombres para poder utilizarlos al destruir
	m_Nombre = nombre;
	m_NombreLock = nombreLock;
	
	try
	{
		if(fifoFileCreated)
		{
			// Inicializo el contador de clientes de la clase
			m_ControlFifo.crear(nombre, ProjectId::MEMORIA_COMPARTIDA);
			
			// Registro al cliente en el contador que corresponde
			TControlFifo controlFifo;
			controlFifo.cerrarRecepcion = false;
			controlFifo.bytesContenidos = 0;
			if(m_ProjectId == ProjectId::FIFO_ESCRITURA)
			{
				controlFifo.cantEscritores = 1;
				controlFifo.cantLectores = 0;
			}
			else
			{
				controlFifo.cantEscritores = 0;
				controlFifo.cantLectores = 1;
			}
			m_ControlFifo.escribirSincronizado(controlFifo);
		}
		else
		{
			// Incremento el contador de clientes de la clase
			m_ControlFifo.unirse(nombre, ProjectId::MEMORIA_COMPARTIDA);
			
			m_ControlFifo.lock();
			TControlFifo controlFifo = m_ControlFifo.leer();
			
			// Verifico si no debo aceptar mas productores
			if(controlFifo.cerrarRecepcion)
			{
				m_ControlFifo.unlock();
				throw BaseException("El fifo no acepta nuevos productores");
			}
			
			// Verifico si debo crear un semaforo para el lado del pipe requerido
			if(m_ProjectId == ProjectId::FIFO_ESCRITURA)
			{
				controlFifo.cantEscritores++;
				if(controlFifo.cantEscritores == 1 && protectAccess)
					crearSemaforo = true;
			}
			else if(m_ProjectId == ProjectId::FIFO_LECTURA)
			{
				controlFifo.cantLectores++;
				if(controlFifo.cantLectores == 1 && protectAccess)
					crearSemaforo = true;
			}
			
			m_ControlFifo.escribir(controlFifo);
			m_ControlFifo.unlock();
		}
		
		// Verifico si debo proteger el acceso al fifo
		if(protectAccess)
		{
			// Creo o abro el semaforo de sincronizacion de acceso correspondiente
			if(crearSemaforo)
				m_Semaforo.crear(nombre,1, m_ProjectId);
			else
				m_Semaforo.abrir(nombre, m_ProjectId);
		}
		
		lockFile.liberarLock();
	}
	catch(const BaseException& e)
	{
		m_ControlFifo.liberar();
		if(crearSemaforo)
			m_Semaforo.eliminar();
		if(fifoFileCreated)
			this->eliminar();
//		lockFile.liberarLock();
		lockFile.cerrar();
		lockFile.eliminar();
		throw e;
	}
	
	lockFile.cerrar();
	lockFile.eliminar();
	
	m_CreatorPid = getpid();
	m_ProtectAccess = protectAccess;
}

void Fifo::destruir()
{
	this->cerrar();
	if(!m_NombreLock.empty() && m_CreatorPid == getpid())
	{
		// Me aseguro que la destruccion del fifo sea sincronizada
		LockFile lockFile;
		try
		{
			lockFile.abrir(m_NombreLock);
			lockFile.tomarLock();
			
			// Decremento el contador de clientes de la clase
			m_ControlFifo.lock();
			TControlFifo cantClientes = m_ControlFifo.leer();
			
			unsigned int cantClientesLadoDestruido;
			if(m_ProjectId == ProjectId::FIFO_ESCRITURA)
				cantClientesLadoDestruido = --cantClientes.cantEscritores;
			else
				cantClientesLadoDestruido = --cantClientes.cantLectores;
			
			m_ControlFifo.escribir(cantClientes);
			
			if(cantClientes.cantEscritores == 0 && cantClientes.cantLectores == 0)
				this->eliminar();
			
			m_ControlFifo.unlock();
			
			if(cantClientesLadoDestruido == 0 && m_Semaforo.estaCreado())
				m_Semaforo.eliminar();
			m_Semaforo.destruir();
			
			m_ControlFifo.liberar();
			
			m_Nombre.clear();
			m_NombreLock.clear();
			
			lockFile.liberarLock();
		}
		catch(const BaseException& e)
		{
			m_ControlFifo.liberar();
			std::cerr << "Ocurrio un error al decremetar el contador " <<
			"de clientes de la clase Fifo:" << e.what() << std::endl;
		}
		
		lockFile.cerrar();
		lockFile.eliminar();
	}
}

void Fifo::cerrar() {
	if(fd != -1)
	{
		close ( fd );
		fd = -1;
	}
}

void Fifo::eliminar() {
	if(!m_Nombre.empty())
	{
		unlink ( m_Nombre.c_str() );
		m_Nombre.clear();
		m_NombreLock.clear();
	}
}

void Fifo::lock()
{
	m_Semaforo.p();
}

void Fifo::unlock()
{
	m_Semaforo.v();
}

bool Fifo::mustExit() const
{
	return (m_ExitHandler != NULL && m_ExitHandler->getGracefulQuit());
}

void  Fifo::setExitHandler(ExitHandler* handler)
{
	m_ExitHandler = handler;
}

bool Fifo::hasContent() const
{
	return (m_ControlFifo.leerSincronizado().bytesContenidos > 0);
}

void Fifo::cerrarRecepcion()
{
	// Marca el flag de control para impedir nuevas recepciones
	m_ControlFifo.lock();
	TControlFifo controlFifo = m_ControlFifo.leer();
	controlFifo.cerrarRecepcion = true;
	m_ControlFifo.escribir(controlFifo);
	m_ControlFifo.unlock();
}

void Fifo::decrementarBytesContenidos(ssize_t cantBytes)
{
	// Decrementa el contador de bytes en la estructura de control
	m_ControlFifo.lock();
	TControlFifo controlFifo = m_ControlFifo.leer();
	controlFifo.bytesContenidos -= cantBytes;
	m_ControlFifo.escribir(controlFifo);
	m_ControlFifo.unlock();
}

void Fifo::incrementarBytesContenidos(ssize_t cantBytes)
{
	// Incrementa el contador de bytes en la estructura de control
	m_ControlFifo.lock();
	TControlFifo controlFifo = m_ControlFifo.leer();
	controlFifo.bytesContenidos += cantBytes;
	m_ControlFifo.escribir(controlFifo);
	m_ControlFifo.unlock();
}

